/*

EC	Effects compiler

(C) 16/8/88  A.Key

Compile effects (and music) into duration-frequency pairs into
a .ce file.

eg:	ms -h -i effectsfile < effectssource

	-h	produce .h c header file with #defines
	-i	produce .inc masm include file with EQUs
	-m	if producing music as opposed to sound effects
	-q n	to define semi quaver length

A typical input file looks like

;
; comments
;
EFFECT_NAME
		10	C#	-1
		20	D	0
		jump	EFFECT_2
EFFECT_2
		20	A	1
		20	1000
		quaver	A#	1
		end

*/

#include <stdio.h>
#include <ctype.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "standard.h"
#include "opt.h"

char	progname [] = "ec";
static char sccs_id [] = "@(#)Effects compiler  16/8/88";

/*...sfatal:0:*/
static void fatal(const char *fmt, ...)
	{
	va_list	vars;
	char	s [256+1];

	va_start(vars, fmt);
	vsprintf(s, fmt, vars);
	va_end(vars);
	fprintf(stderr, "%s: %s\n", progname, s);
	exit(1);
	}
/*...e*/
/*...susage:0:*/
static void usage(void)
	{
	fprintf(stderr, "usage: %s [-him] [-q semi_quaver_length] filename", progname);
	exit(1);
	}
/*...e*/

static BOOLEAN opt_h, opt_i, opt_m;

#define	NOW_STOP	-1
#define	NOW_JUMP	-2
#define	NOW_REST	-3

#define	LINELEN	240
#define	L_NAME	20
#define	FN_LEN	80

static char fn [FN_LEN+1];
static FILE *fp;

static char fn_h [FN_LEN+1];
static FILE *fp_h;

static char fn_inc [FN_LEN+1];
static FILE *fp_inc;

static void trunc(char *base)
	{
	char	*dot, *slash;

	if ( (dot = strrchr(base, '.')) != NULL )
		if ( (slash = strrchr(base, '\\')) == NULL || slash < dot )
			*dot = '\0';
	}

static char *suffix(char *fn, char *base, char *ext)
	{
	strcpy(fn, base);
	strcat(fn, ".");
	return ( strcat(fn, ext) );
	}

static int line_no = 0;
static char line [LINELEN+1];

static BOOLEAN get_line(void)
	{
	int	len;

	do
		{
		line_no++;
		if ( fgets(line, LINELEN+1, stdin) == NULL )
			return ( FALSE );
		}
	while ( *line == ';' );

	if ( (len = strlen(line)) && line [len - 1] == '\n' )
		line [len - 1] = '\0';

	return ( TRUE );
	}


#define	EFFECTS_MAGIC	0x3329

#define	N_SE		20
#define	MAX_BUF		6000

static int buf [MAX_BUF];
static int pointer = 0;

typedef struct id_struct
	{
	char	id_name [L_NAME+1];
	int	id_offset;
	int	id_addr;
	BOOLEAN	id_defined;
	} ID;

static ID id_table [N_SE];
static int id_inx = 0;
static int num = 0;

static int enter_id(char *name, int offset, int ptr, BOOLEAN defined)
	{
	if ( id_inx == N_SE )
		fatal("too many sound effects at line %d", line_no);
	strcpy(id_table [id_inx].id_name, name);
	id_table [id_inx].id_offset  = offset;
	id_table [id_inx].id_addr    = ptr;
	id_table [id_inx].id_defined = defined;
	return ( id_inx++ );
	}

static int index_of(char *name)
	{
	int	i;

	for ( i = 0; i < id_inx; i++ )
		if ( !strcmp(id_table [i].id_name, name) )
			return ( i );
	return ( -1 );
	}

static char *skip_space(char *s)
	{
	while ( *s && isspace(*s) )
		s++;
	return ( s );
	}

static char *get_str(char *t, char *s)
	{
	while ( *s && !isspace(*s) )
		*t++ = *s++;
	*t = '\0';
	return ( s );
	}

static char *get_num(int *p_n, char *s)
	{
	BOOLEAN	negate = FALSE;
	int	t = 0;

	if ( *s == '-' )
		{
		negate = TRUE;
		s++;
		}
	else if ( *s == '+' )
		s++;

	while ( isdigit(*s) )
		t = t * 10 + (*s++ - '0');
	*p_n = ( negate ) ? -t : t;
	return ( s );
	}

typedef struct table_struct
	{
	char	*table_name;
	unsigned int table_num;
	} TABLE;

static int found_at;

static int lookup(char *s, TABLE *table)
	{
	int	i = 0;

	while ( table [i].table_name != NULL )
		if ( strcmp(s, table [i].table_name) )
			i++;
		else
			return ( table [found_at = i].table_num );
	return ( -1 );
	}

static int semi_quaver = 2;

static TABLE durations [] =
	{
		{"q",		2},
		{"q.",		3},
		{"c",		4},
		{"c.",		6},
		{"m",		8},
		{"m.",		12},
		{"b",		16},
		{"b.",		24},
		{"quaver",	2},
		{"quaver.",	3},
		{"crotchet",	4},
		{"crotchet.",	6},
		{"minim",	8},
		{"minim.",	12},
		{"breve",	16},
		{"breve.",	24},
		{NULL,		0}
	};

static char *get_duration(int *p_d, char *s)
	{
	char	name [LINELEN+1];
	int	n;

	s = skip_space(s);
	if ( isdigit(*s) || *s == '-' || *s == '+' )
		return ( get_num(p_d, s) );
	s = get_str(name, s);
	if ( *name == 't' )
		get_num(&n, name + 1);
	else
		if ( (n = lookup(name, durations)) == -1 )
			fatal("illegal duration %s at line %d", name, line_no);
	*p_d = n * semi_quaver;
	return ( s );
	}

/*

Frequecys are derived from the following
table for the notes 3 below middle :-

	Note	Freq/Hz

	A	27.5
	A#	29.135
	B	30.868
	C	32.703
	C#	34.648
	D	36.708
	D#	38.891
	E	41.203
	F	43.654
	F#	46.249
	G	48.999
	G#	51.913

The divisor is calculated via :-

	Divisor = 1.19318E+6 / Freq

For each scale above it, simply divide the divisor by 2

*/

static TABLE frequencys [] =
	{
		{"A",		43388},
		{"A#",		40953},
		{"B",		38654},
		{"C",		36485},
		{"C#",		34437},
		{"D",		32505},
		{"D#",		30680},
		{"E",		28959},
		{"F",		27333},
		{"F#",		25799},
		{"G",		24351},
		{"G#",		22984},
		{NULL,		0}
	};

static char *get_frequency(int *p_f, char *s)
	{
	char	name [LINELEN+1];
	unsigned int f;
	int	scale;

	s = skip_space(s);
	if ( isdigit(*s) || *s == '-' || *s == '+' )
		return ( get_num(p_f, s) );
	s = get_str(name, s);
	s = skip_space(s);
	if ( strcmp(name, "rest") )
		{
		if ( (f = lookup(name, frequencys)) == -1 )
			fatal("illegal note %s at line %d", name, line_no);
		s = get_num(&scale, s);
		if ( opt_m )
			/* simply output note number */
			*p_f = found_at + ( scale + 3 ) * 12;
		else
			/* scale the value from the table and store that */
			{
			while ( scale > -3 )
				{
				f >>= 1;
				scale--;
				}
			*p_f = f;
			}
		}
	else
		*p_f = NOW_REST;

	return ( s );
	}

static void compile_sound_effect(char *name)
	{
	char	*s;
	int	index;

	if ( (index = index_of(name)) == -1 )
		enter_id(name, num++, pointer, TRUE);
	else if ( id_table [index].id_defined )
		fatal("redefinition of %s at line %d", name, line_no);
	else
		{
		id_table [index].id_defined = TRUE;
		id_table [index].id_offset  = num++;
		id_table [index].id_addr    = pointer;
		}

	for ( ;; )
		{
		if ( !get_line() )
			fatal("incomplete sound effect definition at line %d", line_no);
		s = skip_space(line);
		if ( !strncmp(s, "end", 3) )
			{
			buf [pointer++] = NOW_STOP;
			pointer++;
			return;
			}
		if ( !strncmp(s, "jump", 4) )
			{
			s = skip_space(s + 4);
			if ( (index = index_of(s)) == -1 )
				index = enter_id(s, -1, 0, FALSE);
			buf [pointer++] = NOW_JUMP;
			buf [pointer++] = index;
			return;
			}
		s = get_duration(&buf [pointer++], s);
		get_frequency(&buf [pointer++], s);
		}
	}

static void resolve_jumps(void)
	{
	int	i;

	for ( i = 0; i < id_inx; i++ )
		if ( !id_table [i].id_defined )
			fatal("identifier %s not defined", id_table [i].id_name);

	/* know that all identifiers are defined */
	
	for ( i = 0; i < pointer; i += 2 )
		if ( buf [i] == NOW_JUMP )
			buf [i + 1] = id_table [buf [i + 1]].id_offset;
	}

static void output_equates(void)
	{
	int	i;

	for ( i = 0; i < id_inx; i++ )
		{
		if ( opt_h )
			fprintf(fp_h, "#define\t%s\t%d\n", id_table [i].id_name, i);
		if ( opt_i )
			fprintf(fp_inc, "%s\tEQU\t%d\n", id_table [i].id_name, i);
		}
	}

static void output_header(void)
	{
	putw(EFFECTS_MAGIC, fp);
	putw(id_inx, fp);
	putw(pointer, fp);
	}

static void output_index(void)
	{
	int	i;

	for ( i = 0; i < id_inx; i++ )
		putw(id_table [i].id_addr, fp);
	}

static void output_data(void)
	{
	fwrite(buf, sizeof(int), pointer, fp);
	}

int main(int argc, char *argv[])
	{
	char	name [L_NAME+1], *q;

	if ( !proc_opt(&argc, argv, "him", "q") )
		usage();

	if ( argc != 2 )
		usage();

	opt_m = opt("m", NULL);

	if ( opt("q+", &q) )
		{
		if ( !isdigit(*q) )
			fatal("semi quaver length is an unsigned integer");
		semi_quaver = atoi(q);
		}

	trunc(argv [1]);
	while ( get_line() )
		{
		sscanf(line, "%s", name);
		compile_sound_effect(name);
		}
	resolve_jumps();

	if ( (fp = fopen(suffix(fn, argv [1], "ce"), "wb")) == NULL )
		fatal("unable to create %s compiled effect file", argv [1]);

	output_header();
	output_index();
	output_data();
	fclose(fp);

	if ( opt_h = opt("h", NULL) )
		{
		if ( (fp_h = fopen(suffix(fn_h, argv [1], "h"), "w")) == NULL )
			fatal("unable to create %s effect header file", argv [1]);
		fprintf(fp_h, "/*\n\n%s  Sound effect header generated by ec\n\n*/\n\n", fn_h);
		}

	if ( opt_i = opt("i", NULL) )
		{
		if ( (fp_inc = fopen(suffix(fn_inc, argv [1], "inc"), "w")) == NULL )
			fatal("unable to create %s effect include file", argv [1]);
		fprintf(fp_inc, ";\n; %s  Sound effect include file generated by ec\n;\n\n", fn_inc);
		}

	output_equates();

	if ( opt_h ) fclose(fp_h);
	if ( opt_i ) fclose(fp_inc);

	return 0;
	}
